<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>improvedDebounce</title>
</head>
<body>
  <input type="text" id="input"></input>
  <button id="btn">取消事件</button>
  <script type='text/javascript'>
    // 获得带有防抖的目标执行函数
    var debounced = debounce(inputTest, 5000, false, (res) => {
      console.log(res);
    });
	// 按钮点击事件句柄绑定
    btn.onclick = function() {
      // 取消定时器
      debounced.remove();
    }
	  // 输入框输入事件处理函数绑定
    input.addEventListener('input', debounced, false);

    function inputTest() {
      return this.value;
    }
    /** 
     * 设置防抖函数的形参有所不同, 我们多了一个立即执行的配置项
     * 以及一个callback回调函数, 用于接收目标函数返回结果
     * 这里偷了个懒，用了ES6的函数形参默认值
     * callback这一部分按照需求自行修改
     */
    function debounce(fn, delay, now = false, callback) {
      var t = null;
      // 将内部函数定义为函数字面量
      // 大家应该知道函数本质上其实也是一个对象
      // 可以通过 debounced instanceof Object来验证
      var debounced = function() {
        var _self = this,
          args = arguments;
        
        if (t) {
          clearTimeout(t);
        }
        // 如果需要立即执行目标函数即now -> true
        if (now) {
          /** 
           * 记录当前是否存在定时器
           * 因为now设置为true之后的每一次执行都会进入这个判断分支
           * 我们需要判断当前是否是真的"第一次"进入执行
           */
          var trigger = !t;
          // 记录完trigger之后, 设置一个定时器
          // 定时器的目的是在延迟之后清除t
          // 以便延迟时间之后的进入判断为"第一次进入"
          t = setTimeout(function() {
            clearTimeout(t);
            t = null;
          }, delay);
          // 第一次进入执行
          if (trigger) {
            // 立即执行目标函数
            fn.apply(_self, args);
          } else {
            // 若不是第一次执行则执行防抖操作
            t = setTimeout(function() {
              callback(fn.apply(_self, args));
            }, delay);
          }
        } else {
          // 非立即执行情况，正常执行防抖操作
          t = setTimeout(function() {
            callback(fn.apply(_self, args));
            }, delay);
        }
      }

      // 清除当前作用域中的定时器
      debounced.remove = function() {
        clearTimeout(t);
        t = null;
      }

      // 更多方法按照需求定义...

      return debounced;
    }
  </script>
</body>
</html>